package com.wei.czz.common.aspect;

import com.wei.czz.common.annotation.Resubmit;
import com.wei.czz.common.constant.Constant;
import com.wei.czz.common.constant.RedisConstant;
import com.wei.czz.common.enums.ResultEnum;
import com.wei.czz.common.exception.CzzException;
import com.wei.czz.common.po.UserPo;
import com.wei.czz.common.tool.Result;
import com.wei.czz.common.utils.ContextUtils;
import com.wei.czz.common.utils.SecurityUtils;
import com.wei.czz.common.utils.SpringELUtils;
import com.wei.czz.framework.common.handler.redis.RedisStringHandler;
import com.wei.czz.framework.common.handler.redis.RedisHandler;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2025-04-21 11:59
 * className: ResubmitAspect 防重复提交切面类
 * version: 2.0
 * description:
 *  `@AllArgsConstructor`: 构造函数注入bean
 */
@Order(3)
@Aspect
@Component
@AllArgsConstructor
public class ResubmitAspect {

    private static final Logger log = LoggerFactory.getLogger(ResubmitAspect.class);

    private final RedisStringHandler redisStringHandler;

    private final RedisHandler redisHandler;

    /**
     * com.wei.blog.framework包路径下，index模块、common模块和system模块的controller包下，所有的接口方法作为切点
     */
    @Pointcut("@annotation(com.wei.czz.common.annotation.Resubmit)")
    public void annotationPoint() {}

    /**
     * com.wei.blog.framework包路径下，index模块、common模块和system模块的controller包下，所有的接口方法作为切点
     */
    @Pointcut("execution(* com.wei.czz.framework.*.controller.*.*(..))")
    public void controllerPoint() {}

    /**
     * Aop环绕通知方法
     */
    @Around("annotationPoint() && controllerPoint()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {

        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        // 获取目标类名称
        String className = methodSignature.getDeclaringType().getSimpleName();
        // 获取方法名称
        String methodName = methodSignature.getName();

        // 获取Resubmit对象
        Resubmit resubmit = methodSignature.getMethod().getAnnotation(Resubmit.class);
        if (Objects.isNull(resubmit)) {
            log.error("获取Resubmit对象为空。className={} methodName={}", className, methodName);
            throw new CzzException();
        }

        // 方法参数数组
        Object[] args = pjp.getArgs();

        // 请求ip地址
        String requestIp = ContextUtils.get(Constant.REQUEST_IP);

        // 构造接口防重复请求Key
        StringBuilder sb = new StringBuilder();
        sb.append(RedisConstant.STRING).append(Constant.RESUBMIT).append(RedisConstant.SPLIT);
        if (StringUtils.isBlank(resubmit.spEL())) {
            // 构造接口防重复请求Key
            sb.append(requestIp).append(Constant.SPLIT).append(className).append(Constant.SPLIT)
                    .append(methodName).append(Constant.SPLIT).append('[');
            // 入参过滤及格式化
            for (Object arg : args) {
                if (filterArgs(args)) {
                    sb.append(arg).append('、');
                }
            }
            // 替换倒数第一个字符
            sb.setCharAt(sb.length() - 1, ']');

        } else {
            /*
                SpringEL语句不为空，通过spEL语句生成缓存键
                参与spEL语句的参数值不能为空，则否报空指针异常
             */
            // 参数名数组
            String[] argNames = methodSignature.getParameterNames();

            Map<String, Object> map = new HashMap<>();
            for (int index = 0; index < args.length; index++) {
                if (filterArgs(args[index])) {
                    // 可以进行校验，将参数设置到上下文中
                    map.put(argNames[index], args[index]);
                }
            }
            UserPo userPo = SecurityUtils.getUser();
            if (Objects.nonNull(userPo)) {
                // 设置请求用户
                map.put("userPo", userPo);
                // 设置请求用户主键
                map.put(Constant.USER_ID, userPo.getUserId());
            }
            // 请求ip放入el表达式中
            map.put(Constant.REQUEST_IP, requestIp);

            // 解析SpringEL语句
            String value = SpringELUtils.execute(resubmit.spEL(), map, String.class);

            // 追加SpringEL语句执行结果
            sb.append(value);
        }
        // 接口防重复请求Key
        String resubmitKey = sb.toString();

        // 获取限制时长，单位：毫秒
        long millisecond = resubmit.resubmitUnit().toMillis(resubmit.resubmitTime());
        // 最低限制时长1000毫秒
        millisecond = Math.max(1000, millisecond);

        // 操作Redis缓存，添加一对Key-Value值
        Boolean bool = redisStringHandler.setIfAbsent(resubmitKey, Constant.SPLIT, millisecond, TimeUnit.MILLISECONDS);
        log.info("请求防重复校验完成。resubmitKey={} bool={}", resubmitKey, bool);
        if (!bool) {
            // 重复提交，直接返回失败结果
            String message = resubmit.message() + "请求重复，系统拒绝此次请求。";
            // 重复提交，直接抛出异常，不往下执行实际业务
            throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), message);
        }

        long start = System.currentTimeMillis();

        try {

            // 执行目标方法
            Object result = pjp.proceed();

            long end = System.currentTimeMillis();

            if (result instanceof Result) {
                Result<?> r = (Result<?>) result;
                if (!r.isSuccess()) {
                    /*
                        只有处于限制范围内，才能将防重复提交key删除
                     */
                    if (end - start < millisecond) {
                        // 清理缓存键
                        redisHandler.delete(resubmitKey);
                    }
                }
            }

            return result;

        } catch (Throwable throwable) {
            long end = System.currentTimeMillis();
            /*
                只有处于限制范围内，才能将防重复提交key删除
             */
            if (end - start < millisecond) {
                // 清理缓存键
                redisHandler.delete(resubmitKey);
            }

            throw throwable;

        }

    }

    /**
     * 参数过滤
     * 去除一些框架传入的参数对象
     * @param arg 参数对象
     */
    private static boolean filterArgs(Object arg) {
        if (arg instanceof MultipartFile) {
            return false;
        }
        if (arg instanceof HttpServletRequest) {
            return false;
        }
        if (arg instanceof HttpServletResponse) {
            return false;
        }
        if (arg instanceof BindingResult) {
            return false;
        }
        return true;
    }

}
